本文 Tags:命令 变量 教程 清单 册页
开始之前 本节解释这些教程讲授什么内容,以及如何从这些教程获得最大的收益。 关于本系列 Linux Professional Institute(LPI)对初级 (也称为“1 级认证”)和中级 (也称为“2 级认证”)的 Linux 系统管理员进行认证。要想达到 1 级认证,必须通过考试 101 和 102;要想达到 2 级认证,必须通过考试 201 和 202。 developerWorks 提供教程来帮助您准备这 4 门考试。每门考试包含几个主题,每个主题在 developerWorks 上都有一个对应的自学教程。对于 LPI 考试 101,有下面 5 个主题和对应的 developerWorks 教程:
表 1. LPI 考试 101:教程和主题
LPI 考试 101 主题developerWorks 教程教程摘要主题 101LPI 证书 101 考试准备(主题 101): 硬件和体系结构 学习如何使用 Linux 配置系统硬件。在学完这个教程之后,您将掌握 Linux 如何配置现代 PC 上的硬件以及如何查找出问题的地方。 主题 102LPI 101 考试准备: Linux 安装与包管理 介绍 Linux 安装与包管理。在学完这个教程之后,您将掌握 Linux 如何使用硬盘分区、Linux 如何引导以及如何安装和管理软件包。主题 103LPI 101 考试准备: GNU 和 UNIX 命令(本教程)。介绍常用的 GNU 和 UNIX 命令。在学完这个教程之后,您将掌握如何在 bash shell 中使用命令,包括如何使用文本处理命令和过滤器、如何搜索文件和目录以及如何管理进程。 主题 104LPI 104 考试准备: 设备、Linux 文件系统与 Filesystem Hierarchy Standard 敬请期待主题 110LPI 110 考试准备: X Window 系统 敬请期待 要想通过考试 101 和 102(并达到 1 级认证),您应该能够: 在 Linux 命令行上进行操作。 执行简单的维护任务:帮助用户,向更大的系统中添加用户,备份和恢复,关机和重新引导。 安装和配置工作站(包括 X)并将它连接到 LAN,或者通过调制解调器将单独的 PC 连接到互联网。 要想继续准备 1 级认证,请参考 针对 LPI 考试 101 的 developerWorks 教程。请阅读 全套 developerWorks LPI 教程。 Linux Professional Institute 不为任何第三方考试准备资料或技术做担保。 关于本教程 欢迎学习“GNU 和 UNIX 命令”,这是针对 LPI 考试 101 设计的 5 个教程中的第 3 个。在本教程中,您将学习如何使用 GNU 和 UNIX 命令。 本教程是按照这个主题的 LPI 目标组织的。大致来说,权值越高的学习目标,在考试中出的题就越多。
表 2. GNU 和 UNIX 命令:本教程中涉及的考试目标
LPI 考试目标目标权值目标摘要1.103.1 在命令行上进行操作 权值 5使用命令行与 shell 和命令进行交互。这个目标包括输入有效命令和命令序列,定义、引用和导出环境变量,使用命令历史和编辑设施,调用路径中和路径外的命令,使用命令替换,在目录树中递归地应用命令,以及使用手册页了解命令。 1.103.2 使用过滤器处理文本流 权值 6将过滤器应用于文本流。这个目标包括通过文本工具过滤器发送文本文件和输出流,从而修改输出,这需要使用 GNU textutils 包中的标准 UNIX 命令。 1.103.3 执行基本的文件管理 权值 3使用基本 UNIX 命令复制、移动和删除文件和目录。任务包括高级文件管理操作,比如递归地复制多个文件、递归地删除目录以及移动与通配符匹配的文件。这个目标包括使用简单的和高级的通配符指示来引用文件,以及使用 find 根据类型、大小或时间来定位和操作文件。 1.103.4 使用流、管道和重定向 权值 5对流进行重定向并连接它们,从而高效率地处理文本数据。任务包括对标准输入、标准输出和标准错误进行重定向,将一个命令的输出通过管道连接到另一个命令的输入,将一个命令的输出用作另一个命令的参数,以及将输出发送到 stdout 和文件。 1.103.5 创建、监视和杀死进程 权值 5管理进程。这个目标包括了解如何在前台和后台运行作业,将作业从后台转移到前台或者相反,启动在运行时不连接终端的进程,以及让进程在用户注销后继续运行。任务还包括监视活动的进程,对进程进行选择和排序以便显示,向进程发送信号,杀死进程,以及识别并杀死在 X 会话关闭之后没有终止的 X 应用程序。 1.103.6 修改进程执行优先级 权值 3管理进程执行优先级。任务包括以更高或更低的优先级运行程序,判断进程的优先级,以及修改正在运行的进程的优先级。 1.103.7 使用正则表达式搜索文本文件 权值 3使用正则表达式操作文件和文本数据。这个目标包括创建包含几个计数元素的简单正则表达式。还包括使用正则表达式工具在文件系统或文件内容中执行搜索。 1.103.8 使用 vi 执行基本文件编辑操作 权值 1使用 vi 编辑文本文件。这个目标包括 vi 导航,基本 vi 模式,插入、编辑、删除、复制和寻找文本。 前提条件 要想从本教程获得最大的收益,您应该具备 Linux 的基础知识而且拥有一个可以用来实践的 Linux 系统。有时候,程序的不同版本会以不同方式对输出进行格式化,所以您在进行实践时获得的结果可能会与本教程中的清单和图不完全一样。 使用命令行 本节讨论初级管理(LPIC-1)考试 101 的主题 1.103.1 的内容。这个主题的权值是 5。 在本节中,学习以下主题: 使用命令行与 shell 和命令进行交互 有效的命令和命令序列 定义、引用和导出环境变量 命令历史和编辑设施 调用路径中和路径外的命令 使用命令替换 在目录树中递归地应用命令 使用 man(手册)页了解命令 本节简要地介绍 bash shell 的一些主要特性,重点是那些对于认证很重要的特性。但是这个 shell 是一个非常丰富的环境,我们鼓励您进一步探索它。有许多优秀的书籍讨论 UNIX 和 Linux shell,尤其是 bash shell。 bash shell bash shell 是 Linux 可以使用的几种 shell 之一。它也称为 Bourne-again shell,这个名字来自 Stephen Bourne,以前的一个 shell(/bin/sh)的创建者。bash 大体上与 sh 兼容,但是在函数和编程功能方面提供了许多改进。它结合了来自 Korn shell(ksh)和 C shell(csh)的特性,是一种符合 POSIX 的 shell。 在深入研究 bash 之前,要记住 shell 是一种接受并执行命令的程序。它还支持编程构造,允许从比较小的部分构建出复杂的命令。这些复杂的命令(即脚本)可以保存为文件,就成了新的命令。实际上,典型 Linux 系统上的许多命令 就是 脚本。 shell 有一些内置的 命令,比如 cd、break 和 exec。其他命令是外部的。 shell 还使用三种标准 I/O 流: stdin 是标准输入流,它向命令提供输入。 stdout 是标准输出流,它显示来自命令的输出。 stderr 是标准错误流,它显示来自命令的错误输出。 输入流向命令提供输入,输入通常来自终端键盘。输出流输出文本字符,一般是在终端上。终端原来是 ASCII 打字机或显示终端,但是现在常常是图形桌面上的窗口。关于如何对这些标准 I/O 流进行重定向的更多细节,在本教程后面的 流、管道和重定向 一节中讨论。本节主要在较高层面上讨论重定向。 对于本教程的其余部分,假设您知道如何获得 shell 提示符。如果您不知道,developerWorks 文章 “Basic tasks for new Linux developers” 会教您如何执行这个任务和其他基本任务。 如果使用没有图形桌面的 Linux 系统,或者在图形桌面上打开了终端窗口,那么就会进入提示符,可能像清单 1 所示的这样。 清单 1. 一些典型的用户提示符
[db2inst1@echidna db2inst1]$ ian@lyrebird:~> $
如果作为根用户(即超级用户)登录,那么提示符可能像清单 2 所示的一样。 清单 2. 超级用户(根)提示符示例
[root@echidna root]# lyrebird:~ # #
根用户有相当大的能力,所以使用它要谨慎。在具有根特权时,大多数提示符的末尾有一个磅符号(#)。一般用户特权常常用另一个字符表示,通常是美圆符号($)。您机器上的实际提示符可能看起来与本教程中的例子不一样。您的提示符可能包含用户名、主机名、当前目录、输出提示符的日期或时间等等。 本教程中的约定 针对 LPI 101 和 102 考试的这些 developerWorks 教程包含一些取自真实 Linux 系统的代码示例,使用了这些系统的默认提示符。我们的根提示符的末尾有 #,所以可以将它们与一般用户提示符(末尾有 $)区分开。在关于这个主题的许多书中都采用这种约定。在任何例子中都要仔细注意提示符。 命令和序列 现在有提示符了,我们来看看可以用它做什么。shell 的主要功能是解释用户的命令,从而使用户可以与 Linux 系统进行交互。在 Linux(和 UNIX)系统上,命令有一个命令名,然后是选项 和参数。一些命令没有选项和参数,一些命令有选项但没有参数,其他命令没有选项但有参数。 如果一行包含 # 字符,那么此行上的所有其余字符都被忽略。所以 # 字符既可以表示注释,也可以代表根提示符。具体意义应该很容易从上下文判断出来。 echo echo 命令将它的参数输出(即回显)到终端上,如清单 3 所示。 清单 3. echo 示例
[ian@echidna ian]$ echo Word Word [ian@echidna ian]$ echo A phrase A phrase [ian@echidna ian]$ echo Where are my spaces? Where are my spaces? [ian@echidna ian]$ echo "Here are my spaces." # plus comment Here are my spaces.
在清单 3 的第 3 个示例中,所有多余的空白在输出中压缩成单一空格。要避免这种情况,需要引用 字符串,可以使用双引号(")或单引号(')。bash 使用空白 (比如空格、制表符和新行字符)将输入行分割为记号(token),然后传递给命令。对字符串进行引用会保留多余的空白,并使整个字符串成为一个记号。在上面的例子中,命令名后面的每个记号都是一个参数,所以分别有 1、2、4 和 1 个参数。 echo 命令有两个选项。一般情况下,echo 将在最后在输出中附加一个新行字符。可以使用 -n 选项抑制这种行为。使用 -e 选项使某些用反斜线进行转义的字符具有特殊意义。表 3 显示了一部分转义字符。
表 3. echo 和转义的字符
转义 序列功能\a警报(铃声)\b退格\c抑制末尾的新行字符(与 -n 选项的功能相同)\f换页(在视频显示器上就会清空屏幕)\n新行\r回车\t水平制表符 转义和行延续 在 bash 中使用反斜线有一个小问题。如果反斜线字符(\)不在引号中,那么它就作为转义,让 bash 保留后面字符的字面意义。这对于特殊的 shell 元字符是必要的,这个问题稍后讨论。这个规则有一个例外:反斜线后面跟着新行会使 bash 把这些字符序列视为行延续请求。这对于将长的行进行分割很方便,尤其是在 shell 脚本中。 要想让 echo 命令或使用相似转义控制字符的众多命令能够正确地处理上面描述的序列,必须将转义序列包含在引号中,或者作为引号中的字符串的一部分,否则就要使用第二个反斜线,让 shell 保留序列的字面意义。清单 4 显示 \ 的各种用法。 清单 4. 更多的 echo 示例
[ian@echidna ian]$ echo -n No new line No new line[ian@echidna ian]$ echo -e "No new line\c" No new line[ian@echidna ian]$ echo "A line with a typed > return" A line with a typed return [ian@echidna ian]$ echo -e "A line with an escaped\nreturn" A line with an escaped return [ian@echidna ian]$ echo "A line with an escaped\nreturn but no -e option" A line with an escaped\nreturn but no -e option [ian@echidna ian]$ echo -e Doubly escaped\\n\\tmetacharacters Doubly escaped metacharacters [ian@echidna ian]$ echo Backslash \ > followed by newline \ > serves as line continuation. Backslash followed by newline serves as line continuation.
注意,如果在输入的行中有不匹配的引号,那么 bash 会显示一个特殊的提示符(>)。输入字符串延续到第二行并包含一个新行字符。 bash shell 元字符和控制操作符 bash 有几个元字符,如果不在引号中的话,这些字符也将输入分割为单词。 除了空格之外,还有 '|'、'&'、';'、'('、')'、'<' 和 '>'。在本教程的其他几节中将详细讨论其中一部分元字符。目前要注意,如果希望在文本中包含元字符,就必须将它放在引号中,或者用反斜线(\)进行转义,如清单 4 所示。 新行字符和某些元字符或元字符对也作为控制操作符。 这包括 '||'、'&&'、'&'、';'、';;'、'|'、'(' 和 ')'。这些控制操作符中的一部分允许创建命令的序列 或列表。 最简单的命令序列只是由分号(;)分隔的两个命令。每个命令依次执行。在任何可编程环境中,命令都会返回一个表示成功或失败的标志;Linux 命令常常返回零值来表示成功,返回非零值表示失败。可以使用 && 和 || 控制操作符在命令列表中引入一些条件处理。如果用控制操作符 && 分隔两个命令,那么第二个命令当且仅当第一个命令返回值为零的退出码时执行。如果用 || 分隔命令,那么第二个命令当且仅当第一个命令返回非零退出码时执行。清单 5 显示了使用 echo 命令的一些命令序列。这里没有什么有意思的事儿,因为 echo 只是返回 0,但是在后面使用更多命令时您将看到更多的例子。 清单 5. 命令序列
[ian@echidna ian]$ echo line 1;echo line 2; echo line 3 line 1 line 2 line 3 [ian@echidna ian]$ echo line 1&&echo line 2&&echo line 3 line 1 line 2 line 3 [ian@echidna ian]$ echo line 1||echo line 2; echo line 3 line 1 line 3
退出 可以使用 exit 命令终止 shell。还可以可选地通过参数提供退出码。如果在图形桌面上的终端窗口中运行 shell,那么窗口将关闭。同样,如果使用 ssh 或 telnet 等命令连接到了远程系统,那么连接将终止。在 bash shell 中,还可以按住 Ctrl 键并按下 d 键来退出。 我们来看看另一个控制操作符。如果将命令或命令列表放在圆括号中,那么命令或命令序列会在一个子 shell 中执行,所以 exit 命令会退出子 shell,而不是退出您当前工作的 shell。清单 6 显示用 && 和 || 进行组合的一个例子。 清单 6. 子 shell 和序列
[ian@echidna ian]$ (echo In subshell; exit 0) && echo OK || echo Bad exit In subshell OK [ian@echidna ian]$ (echo In subshell; exit 4) && echo OK || echo Bad exit In subshell Bad exit
本教程中稍后会使用更多的命令序列。 环境变量 在 bash shell 中运行时,许多东西组成了环境,比如提示符的形式、主目录、工作目录、shell 的名称、已经打开的文件、已经定义的函数等等。环境包含许多变量,可能由 bash 设置,也可能由用户设置。bash shell 还允许有 shell 变量,可以将这些变量导出 到环境中,供 shell 中运行的其他进程使用,或者供从当前 shell 产生的其他 shell 使用。 环境变量和 shell 变量都有名称。通过在变量名前面加 '$' 来引用变量值。表 4 显示了一些常见的 bash 环境变量。
表 4. 一些常见的 bash 环境变量
名称功能USER已登录用户的名称UID已登录用户的数字用户 idHOME用户的主目录PWD当前工作目录SHELLshell 的名称$进程 id(即正在运行的 bash shell 进程或其他进程的 PID)PPID启动这个进程的进程的进程 id(即父进程的 id)?最后一个命令的退出码 清单 7 显示了通过这些常见的 bash 变量可以看到什么。 清单 7. 环境和 shell 变量
[ian@echidna ian]$ echo $USER $UID ian 500 [ian@echidna ian]$ echo $SHELL $HOME $PWD /bin/bash /home/ian /home/ian [ian@echidna ian]$ (exit 0);echo $?;(exit 4);echo $? 0 4 [ian@echidna ian]$ echo $$ $PPID 30576 30575 如果不使用 bash,应该怎么办?
bash shell 是大多数 Linux 发行版上的默认 shell。如果不是运行在 bash shell 之下,那么可以考虑用以下方式之一对 bash shell 进行实践。 使用 chsh -s /bin/bash 命令来改变默认 shell。这一修改将在下一次登录时生效。 使用 su - $USER -s /bin/bash 命令创建另一个进程,作为当前 shell 的子进程。新进程将是使用 bash 的登录 shell。 创建一个默认采用 bash shell 的 id,以便准备 LPI 考试。 创建或设置 shell 变量的方式是输入名称,后面直接跟着等号(=)。变量是大小写敏感的,所以 var1 和 VAR1 是不同的变量。按照约定,变量(尤其是导出的变量)是大写的,但这不是必须的。从技术上说,$$ 和 $? 是 shell 参数 而不是变量。只能引用它们,不能赋值。 在创建 shell 变量时,常常希望将它导出 到环境中,使得从这个 shell 启动的其他进程可以使用它。导出的变量对于父 shell 是 不 可用的。使用 export 命令导出变量名。作为 bash 中的快捷方式,可以在一步中同时对变量进行赋值和导出。 为了说明赋值和导出,我们在 bash shell 中运行 bash 命令,然后从这个新的 bash shell 运行 Korn shell(ksh)。我们将使用 ps 命令来显示正在运行的命令的相关信息。在本教程后面学习 进程状态 时,将进一步了解 ps。 清单 8. 更多的环境和 shell 变量
[ian@echidna ian]$ ps -p $$ -o "pid ppid cmd" PID PPID CMD 30576 30575 -bash [ian@echidna ian]$ bash [ian@echidna ian]$ ps -p $$ -o "pid ppid cmd" PID PPID CMD 16353 30576 bash [ian@echidna ian]$ VAR1=var1 [ian@echidna ian]$ VAR2=var2 [ian@echidna ian]$ export VAR2 [ian@echidna ian]$ export VAR3=var3 [ian@echidna ian]$ echo $VAR1 $VAR2 $VAR3 var1 var2 var3 [ian@echidna ian]$ echo $VAR1 $VAR2 $VAR3 $SHELL var1 var2 var3 /bin/bash [ian@echidna ian]$ ksh $ ps -p $$ -o "pid ppid cmd" PID PPID CMD 16448 16353 ksh $ export VAR4=var4 $ echo $VAR1 $VAR2 $VAR3 $VAR4 $SHELL var2 var3 var4 /bin/bash $ exit $ [ian@echidna ian]$ echo $VAR1 $VAR2 $VAR3 $VAR4 $SHELL var1 var2 var3 /bin/bash [ian@echidna ian]$ ps -p $$ -o "pid ppid cmd" PID PPID CMD 16353 30576 bash [ian@echidna ian]$ exit [ian@echidna ian]$ ps -p $$ -o "pid ppid cmd" PID PPID CMD 30576 30575 -bash [ian@echidna ian]$ echo $VAR1 $VAR2 $VAR3 $VAR4 $SHELL /bin/bash
注意: 在这个序列开始时,bash shell 的 PID 为 30576。 第二个 bash shell 的 PID 为 16353,它的父 shell 的 PID 是 30576,也就是原来的 bash shell。 我们在第二个 bash shell 中创建 VAR1、VAR2 和 VAR3,但是只导出 VAR2 和 VAR3。 在 Korn shell 中,创建 VAR4。echo 命令只显示了 VAR2、VAR3 和 VAR4 的值,这证明 VAR1 没有导出。尽管提示符改变了,但是 SHELL 变量的值没有改变。您觉得吃惊吗?不能总是依赖于 SHELL 来了解正在运行哪个 shell,但是 ps 命令可以指出实际的命令。注意,ps 在第一个 bash shell 前面放了一个连字符(-),表示这是登录 shell。 回到第二个 bash shell,可以看到 VAR1、VAR2 和 VAR3。 最后,在返回原来的 shell 时,没有新变量仍然存在。 前面讨论引用时提到,可以使用单引号,也可以使用双引号。这两种引号之间有一个重要的差异。shell 将双引号(")之间的 shell 变量展开,但是在使用单引号(')时不进行展开。在前面的示例中,我们在 shell 中启动另一个 shell,并获得新的进程 id。使用 -c 选项,可以将命令传递给另一个 shell,这个 shell 将执行命令并返回。如果将带引号的字符串作为命令传递,那么外层 shell 将去掉引号并传递字符串。如果使用双引号,那么变量展开发生在传递字符串 之前,所以结果可能不是您希望的。shell 和命令将运行在另一个进程中,所以它们有不同的 PID。清单 9 演示了这些概念。顶层 bash shell 的 PID 突出显示。 清单 9. 引用和 shell 变量
[ian@echidna ian]$ echo "$SHELL" '$SHELL' "$$" '$$' /bin/bash $SHELL 19244 $$ [ian@echidna ian]$ bash -c "echo Expand in parent $$ $PPID" Expand in parent 19244 19243 [ian@echidna ian]$ bash -c 'echo Expand in child $$ $PPID' Expand in child 19297 19244
到目前为止,所有的变量引用都以空格结束,所以变量名在哪里结束是很明显的。实际上,变量名只能由字母、数字或下划线字符组成。当找到另一个字符时,shell 就知道变量名结束了。有时候,可能需要在含义不明确的表达式中使用变量。在这种情况下,可以使用花括号使变量名明确,见清单 10。 清单 10. 对变量名使用花括号
[ian@echidna ian]$ echo "-$HOME/abc-" -/home/ian/abc- [ian@echidna ian]$ echo "-$HOME_abc-" -- [ian@echidna ian]$ echo "-${HOME}_abc-" -/home/ian_abc-
env env 命令如果不带任何选项或参数,就显示当前的环境变量。还可以使用它在定制的环境中执行命令。-i(或只是 -)选项在运行命令之前清理当前环境,而 -u 选项清除您不希望传递的环境变量。 清单 11 显示了不带任何参数的 env 命令的部分输出,然后是三个不用父环境调用不同 shell 的例子。在讨论之前,先认真看看这些例子。 清单 11. env 命令
[ian@echidna ian]$ env HOSTNAME=echidna TERM=xterm SHELL=/bin/bash HISTSIZE=1000 SSH_CLIENT=9.27.89.137 4339 22 SSH_TTY=/dev/pts/2 USER=ian ... _=/bin/env OLDPWD=/usr/src [ian@echidna ian]$ env -i bash -c 'echo $SHELL; env' /bin/bash PWD=/home/ian SHLVL=1 _=/bin/env [ian@echidna ian]$ env -i ksh -c 'echo $SHELL; env' _=/bin/env PATH=/bin:/usr/bin [ian@echidna ian]$ env -i tcsh -c 'echo $SHELL; env' SHELL: Undefined variable.
注意,bash 已经设置了 SHELL 变量,但是没有将它导出到环境中,尽管 bash 在环境中创建了另外三个变量。在 ksh 示例中,有两个环境变量,但是试图回显 SHELL 变量的值时只得到了一个空行。最后,tcsh 没有创建任何环境变量,并在我们试图引用 SHELL 的值时产生一个错误。 清除和设置 清单 11 显示了几种 shell 在处理变量和环境方面的行为差异。本教程主要关注 bash,但是您应该明白所有 shell 并不采用相同的处理方式。另外,根据 shell 是否是登录 shell,处理方式也会有差异。在目前,我们只需知道登录 shell 就是在系统上进行登录时获得的 shell;如果愿意,可以作为登录 shell 启动其他 shell。上面使用 env -i 启动的三个 shell 不是登录 shell。请尝试将 -l 选项传递给 shell 命令本身,从而体会登录 shell 的行为有什么不同。 现在,我们来研究在这三个非登录 shell 中尝试显示 SHELL 变量值的结果: 在 bash 启动时,设置了 SHELL 变量,但是没有将它自动导出到环境中。 在 ksh 启动时,没有设置 SHELL 变量。但是引用未定义的环境变量相当于引用具有空值的环境变量。 在 tcsh 启动时,没有设置 SHELL 变量。在这种情况下,默认行为与 ksh(和 bash)的不同之处在于,在尝试使用变量时会报告一个错误。 可以使用 unset 命令将变量从 shell 变量列表中清除。如果变量已经导出到环境中,那么也会从环境中删除它。可以使用 set 命令控制 bash(或其他 shell)的工作方式的许多方面。set 是 shell 内置的命令,所以各种选项是与 shell 相关的。在 bash 中,-u 选项让 bash 在遇到未定义变量时报告错误,而不是像对待具有空值的已定义变量一样。可以在 set 中使用 - 打开各种选项,使用 + 关闭它们。可以使用 echo $- 显示当前设置的选项。 清单 12. unset 和 set
[ian@echidna ian]$ echo $- himBH [ian@echidna ian]$ echo $VAR1 [ian@echidna ian]$ set -u;echo $- himuBH [ian@echidna ian]$ echo $VAR1 bash: VAR1: unbound variable [ian@echidna ian]$ VAR1=v1 [ian@echidna ian]$ VAR1=v1;echo $VAR1 v1 [ian@echidna ian]$ unset VAR1;echo $VAR1 bash: VAR1: unbound variable [ian@echidna ian]$ set +u;echo $VAR1;echo $- himBH
如果不带任何选项使用 set 命令,那么它显示所有 shell 变量及其值(如果有的话)。还有另一个命令,declare,可以使用它创建、导出和显示 shell 变量的值。可以使用手册页研究其他 set 选项和 declare 命令。在本节后面我们将讨论 手册页。 exec 本节中研究的最后一个命令是 exec。可以使用 exec 命令运行另一个程序来 替代 当前 shell。清单 13 启动一个子 bash shell,然后使用 exec 用 Korn shell 替代它。在退出 Korn shell 时,会回到原来的 bash shell(在这个例子中,PID 是 22985)。 清单 13. 使用 exec
[ian@echidna ian]$ echo $$ 22985 [ian@echidna ian]$ bash [ian@echidna ian]$ echo $$ 25063 [ian@echidna ian]$ exec ksh $ echo $$ 25063 $ exit [ian@echidna ian]$ echo $$ 22985
命令历史 如果您一边阅读本教程,一边输入命令,那么可能会发现常常要多次使用同一个命令,要么是完全一样,要么是只有细微差异。好消息是 bash shell 可以维护命令的历史。 在默认情况下,历史功能是打开的。可以使用 set +o history 命令关闭它,使用 set -o history 重新打开。环境变量 HISTSIZE 告诉 bash 保留多少历史行。还有许多其他设置可以控制历史如何工作以及如何管理历史。完整的细节请参考 bash 手册页。 可以通过历史设施使用的一些命令如下: history显示全部历史history N 显示历史中最后 N 行history -d N 从历史中删除第 N 行;例如,如果这一行包含密码,就可能需要这么做!!最近的命令!N 第 N 个历史命令!-N 历史中倒数第 N 个命令(!-1 相当于 !!)!#正在输入的当前命令!string 以 string 开头的最近的命令!?string?包含 string 的最近的命令 还可以使用冒号(:)后面跟着某些值来访问或修改一个历史命令的某些部分。清单 14 演示了一些历史功能。 清单 14. 操作历史
[ian@echidna ian]$ echo $$ 22985 [ian@echidna ian]$ env -i bash -c 'echo $$' 1542 [ian@echidna ian]$ !! env -i bash -c 'echo $$' 1555 [ian@echidna ian]$ !ec echo $$ 22985 [ian@echidna ian]$ !en:s/$$/$PPID/ env -i bash -c 'echo $PPID' 22985 [ian@echidna ian]$ history 6 1097 echo $$ 1098 env -i bash -c 'echo $$' 1099 env -i bash -c 'echo $$' 1100 echo $$ 1101 env -i bash -c 'echo $PPID' 1102 history 6 [ian@echidna ian]$ history -d1100 清单 14 中的命令做了下面这些事: 回显当前 shell 的 PID 在新的 shell 中运行 echo 命令并回显 shell 的 PID 重新运行最后一个命令 重新运行以 'ec' 开头的最后一个命令;这会重新运行这个例子中的第一个命令 重新运行以 'en' 开头的最后一个命令,但是用 '$PPID' 替代 '$$',所以会显示父进程的 PID 显示历史中最后 6 个命令 删除历史项 1100,即最后一个 echo 命令 还可以交互式地编辑历史。bash shell 使用 readline 库来管理命令编辑和历史。在默认情况下,用来在历史中进行移动和编辑的键和键组合与 GNU Emacs 编辑器中的相似。Emacs 键组合常常表示成 C-x 或 M-x,其中的 x 是常规键,C 和 M 分别是控制 和元 键。在典型的 PC 系统上,Ctrl 键作为 Emacs 控制键,Alt 键作为元键。表 5 总结了一些历史编辑功能。除了表 5 所示的键组合以外,光标移动键(比如右、左、上和下箭头)以及 Home 和 End 键常常按照符合逻辑的方式工作。在手册页中可以找到其他功能以及如何使用 readline init 文件(常常是主目录中的 inputrc)来定制这些选项。
表 5. 用 emacs 命令进行历史编辑
命令常用 PC 键说明C-f右箭头向右移动一格C-b左箭头向左移动一格M-fAlt-f移动到下一个单词的开头;GUI 环境常常用这个键组合来打开窗口的 File 菜单M-bAlt-b移动到前一个单词的开头C-aHome移动到行的开头C-eEnd移动到行的末尾BackspaceBackspace删除光标前面的字符C-dDel删除光标后面的字符(Del 和 Backspace 功能可能配置为相反的意义)C-kCtrl-k删除(kill)到行的末尾并保存删除的文本供以后使用M-dAlt-d删除(kill)到单词的末尾并保存删除的文本供以后使用C-yCtrl-y取回由删除命令删除的文本 如果您喜欢使用与 vi 相似的编辑模式操作历史,那么可以使用命令 set -o vi 切换到 vi 模式。使用 set -o emacs 返回到 emacs 模式。在 vi 模式中获取命令时,最初处于 vi 的插入模式中。关于 vi 编辑器的更多细节在 用 vi 进行文件编辑 一节中介绍。 路径 一些 bash 命令是内置的,其他命令是外部的。我们现在来看看外部命令,如何运行它们,以及如何知道命令是否是内部的。 shell 在哪里寻找命令? 外部命令只是文件系统中的文件。本教程后面的一节 基本文件管理 和针对主题 104 的教程讨论了更多细节。在 Linux 和 UNIX 系统上,所有文件都作为一个大型树结构的一部分,这个树结构的根是 /。在到目前为止提供的示例中,我们的当前目录都是用户的主目录。非根用户常常在 /home 目录中有自己的主目录,比如我的主目录是 /home/ian。根用户的主目录常常是 /root。如果输入命令名,那么 bash 会在您的路径 中寻找这个命令,路径是在 PATH 环境变量中指定的以分号分隔的目录列表。 如果想知道在输入某一字符串时执行的是什么命令,使用 which 或 type 命令。清单 15 显示我的默认路径以及几个命令的位置。 清单 15. 寻找命令位置
[ian@echidna ian]$ echo $PATH /usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin:/home/ian/bin [ian@echidna ian]$ which bash env zip xclock echo set ls alias ls='ls --color=tty' /bin/ls /bin/bash /bin/env /usr/bin/zip /usr/X11R6/bin/xclock /bin/echo /usr/bin/which: no set in (/usr/local/bin:/bin:/usr/bin:/usr/X11R6/b in:/home/ian/bin) [ian@echidna ian]$ type bash env zip xclock echo set ls bash is /bin/bash env is /bin/env zip is /usr/bin/zip xclock is /usr/X11R6/bin/xclock echo is a shell builtin set is a shell builtin ls is aliased to `ls --color=tty'
注意,路径中的所有目录都以 /bin 结尾。这是一种常见的约定,但不是要求。which 命令报告 ls 命令是一个别名(alias),而 set 命令无法找到。在这种情况下,可以认为它不存在或者是内置的命令。type 命令报告 ls 命令是一个别名,但是它识别出 set 命令是一个 shell 内置命令。它还报告有一个内置的 echo 命令以及 /bin 目录中的命令,which 也找到了这些命令。这两个命令还以不同的次序产生输出。 我们看到 ls 命令(用于列出目录内容)是一个别名。别名可以方便地将某些命令配置为使用不同的默认设置集,或者为命令提供替代名。在我们的示例中,--color=tty 选项使得目录列表按照文件或目录类型以不同颜色显示。尝试运行 dircolors --print-database,从而了解如何控制颜色编码以及用哪种颜色表示哪种文件。 这些命令都有其他选项。根据需要,可以使用其中任意一个命令。如果我确定要寻找的是可执行文件并只需要它的完整路径,那么倾向于使用 which。我发现 type 会提供更精确的信息,有时需要在 shell 脚本中使用。 运行其他命令 在清单 15 中看到,可执行文件的完整路径以 /(根目录)开头。例如,xclock 程序实际上是 /usr/X11R6/bin/xclock,即 /usr/X11R6/bin 目录中的一个文件。如果命令 不 在 PATH 设置中,那么仍然可以通过指定路径以及命令名来运行它。可以使用两种类型的路径: 绝对 路径是以 / 开头的路径,比如在清单 15 中看到的那些(/bin/bash、/bin/env 等等)。 相对 路径是相对于当前工作目录 的(当前工作目录由 pwd 命令报告)。这些路径不以 / 开头,但是至少包含一个 /。 使用绝对路径时可以不管当前工作目录是什么,但是只有当命令在当前目录中时才可能使用相对路径。假设您正在主目录的 mytestbin 子目录中开发传统 “Hello World!” 程序的一个新版本。可以使用相对路径以 mytestbin/hello 的形式运行这个命令。在路径中可以使用两个特殊名称;一个点(.)是指当前目录,两个点(..)是指当前目录的父目录。因为主目录常常不在 PATH 中(而且一般来说应该不在),对于希望从主目录运行的任何可执行文件,需要显式地提供路径。例如,如果在主目录中有 hello 程序的副本,那么可以使用命令 ./hello 运行它。可以在绝对路径中使用 . 和 ..,尽管单个 . 在这种情况下不太有用。还可以使用波浪号(~)表示自己的主目录,用 ~username 表示名为 username 的用户的主目录。清单 16 中给出了一些示例。 清单 16. 绝对和相对路径
[ian@echidna ian]$ /bin/echo Use echo command rather than builtin Use echo command rather than builtin [ian@echidna ian]$ /usr/../bin/echo Include parent dir in path Include parent dir in path [ian@echidna ian]$ /bin/././echo Add a couple of useless path components Add a couple of useless path components [ian@echidna ian]$ pwd # See where we are /home/ian [ian@echidna ian]$ ../../bin/echo Use a relative path to echo Use a relative path to echo [ian@echidna ian]$ myprogs/hello # Use a relative path with no dots -bash: myprogs/hello: No such file or directory [ian@echidna ian]$ mytestbin/hello # Use a relative path with no dots Hello World! [ian@echidna ian]$ ./hello # Run program in current directory Hello World! [ian@echidna mytestbin]$ ~/mytestbin/hello # run hello using ~ Hello World! [ian@echidna ian]$ ../hello # Try running hello from parent -bash: ../hello: No such file or directory
改变工作目录 正如可以从系统中的各个目录执行程序,也可以使用 cd 命令改变当前的工作目录。cd 的参数必须是目录的绝对路径或相对路径。对于这个命令,可以在路径中使用 .、..、~ 和 ~username。如果不带参数使用 cd,那么当前工作目录将改变为主目录。用一个连字符(-)作为参数意味着恢复原来的工作目录。主目录存储在 HOME 环境变量中,原来的工作目录存储在 OLDPWD 变量中,所以 cd 相当于 cd $HOME,cd - 相当于 cd $OLDPWD。通常我们会说改变目录,而不完整地说改变当前工作目录。 对于这个命令,还有一个环境变量 CDPATH,它包含在解析相对路径时应该搜索的以分号分隔的路径集(除了当前工作目录之外)。如果解析使用了来自 CDPATH 的路径,那么 cd 将输出得到的目录的完整路径。在正常情况下,成功的目录改变不会产生输出,只是提示符可能会改变。清单 17 给出了一些示例。 清单 17. 改变目录
[ian@echidna home]$ cd /;pwd / [ian@echidna /]$ cd /usr/X11R6;pwd /usr/X11R6 [ian@echidna X11R6]$ cd ;pwd /home/ian [ian@echidna ian]$ cd -;pwd /usr/X11R6 /usr/X11R6 [ian@echidna X11R6]$ cd ~ian/..;pwd /home [ian@echidna home]$ cd ~;pwd /home/ian [ian@echidna ian]$ export CDPATH=~ [ian@echidna mytestbin]$ cd /;pwd / [ian@echidna /]$ cd mytestbin /home/ian/mytestbin
递归地应用命令 许多 Linux 命令可以递归地应用于一个目录树中的所有文件。例如,ls 命令有一个 -R 选项用于递归地列出目录内容,cp、mv、rm 和 diff 命令都有 -r 选项用于递归地应用它们。基本文件管理 一节将详细地讨论命令的递归应用。 命令替换 bash shell 有一种非常强大的功能,允许将一个命令的结果用作另一个命令的输入;这称为命令替换。实现方法是将您希望使用其结果的命令封闭在反单引号(`)中。这仍然是常用方法,但是有另一个方法可以更容易处理多个嵌套的命令,即将命令封闭在 $( 和 ) 之间。 在前一个教程 “LPI 101 考试准备(主题 102):Linux 安装与包管理” 中,我们看到 rpm 命令可以指出一个命令来自哪个包;我们使用命令替换功能作为简化技术。现在您知道我们当时究竟在做什么了。 在 shell 脚本中,命令替换是一种很有价值的工具,在命令行上也有用。清单 18 给出了一些示例,它们从一个相对路径获得绝对路径,寻找哪个 RPM 提供了 /bin/echo 命令,并(作为根用户)列出了硬盘上三个分区的标签。最后一个示例使用 seq 命令产生一系列整数。 清单 18. 命令替换
[ian@echidna ian]$ echo '../../usr/bin' dir is $(cd ../../usr/bin;pwd) ../../usr/bin dir is /usr/bin [ian@echidna ian]$ which echo /bin/echo [ian@echidna ian]$ rpm -qf `which echo` sh-utils-2.0.12-3 [ian@echidna ian]$ su - Password: [root@echidna root]# for n in $(seq 7 9); do echo p$n `e2label /dev/hda$n`;done p7 RH73 p8 SUSE81 p9 IMAGES
手册页 本教程这一节中的最后一个主题是,如何通过手册页和其他文档来源获得 Linux 命令的文档。 手册页和小节 文档的主要(且传统的)来源是手册页,可以使用 man 命令访问手册页。图 1 展示了 man 命令本身的手册页。使用命令 man man 来显示这一信息。 图 1. man 命令的手册页

图 1 显示了手册页中的一些典型项目: 标题,即命令名后面跟着圆括号中的小节号 命令名和在同一手册页中描述的任何相关命令 这个命令可以应用的选项和参数的摘要 对命令的简短描述 关于每个选项的详细信息 可能会找到使用方法、如何报告 bug 、作者信息以及相关命令列表等其他信息。例如,man 的手册页告诉我们相关命令(及其手册页小节)是:
apropos(1)、whatis(1)、less(1)、groff(1) 和 man.conf(5)。 有 8 个常用的手册页小节。在安装包时常常会安装手册页,所以如果还没有安装某个包,就可能还没有它的手册页。同样,某些手‹‹册页小节可能是空的,或者几乎是空的。常用手册页小节以及一些内容示例如下: 用户命令(env、ls、echo、mkdir、tty) 系统调用或内核功能(link、sethostname、mkdir) 库例程(acosh、asctime、btree、locale、XML::Parser) 与设备相关的信息(isdn_audio、mouse、tty、zero) 文件格式描述(keymaps、motd、wvdial.conf) 游戏(注意,现在的许多游戏是图形化的,除了手册页系统之外,还有图形化的帮助) 杂项命令(arp、boot、regex、unix utf8) 系统管理(debugfs、fdisk、fsck、mount、renice、rpm) 可能有的其他小节包括 9(Linux 内核文档)、n(新文档)、o(旧文档)和 l(本地文档)。 一些项目会在多个小节中出现。我们的示例说明 mkdir 在小节 1 和 2 中都出现了,tty 也同时出现在小节 1 和 4 中。可以指定某个小节,例如 man 4 tty 或 man 2 mkdir,还可以指定 -a 选项来列出所有可应用的手册页小节。 在图中可能会注意到,man 有许多选项,可以自己研究。目前,让我们看看与 man 相关的一些“参见”命令。 参见 与 man 相关的两个重要命令是 whatis 和 apropos。whatis 命令搜索您提供的名称的手册页并显示来自适当手册页的名称信息。apropos 命令对手册页进行关键词搜索并列出包含关键词的手册页。清单 19 演示这些命令。 清单 19. whatis 和 apropos 示例
[ian@lyrebird ian]$ whatis man man (1) - format and display the on-line manual pages man (7) - macros to format man pages man [manpath] (1) - format and display the on-line manual pages man.conf [man] (5) - configuration data for man [ian@lyrebird ian]$ whatis mkdir mkdir (1) - make directories mkdir (2) - create a directory [ian@lyrebird ian]$ apropos mkdir mkdir (1) - make directories mkdir (2) - create a directory mkdirhier (1x) - makes a directory hierarchy
顺便说一下,如果无法找到 man.conf 的手册页,可以试着运行 man man.config 。 man 命令使用分页程序让输出在显示器上分页显示。在大多数 Linux 系统上,分页程序可能是 less 程序。另一个选择是比较老的 more 程序。如果希望打印手册页,那么指定 -t 选项来对手册页进行格式化,以便使用 groff 或 troff 程序进行打印。 less 分页程序有几个命令,可以帮助在显示的输出中搜索字符串。请使用 man less 来进一步了解 /(向前搜索)、?(向后搜索)、n(重复前一次搜索)以及其他命令。 其他文档来源 除了可以从命令行访问的手册页之外,Free Software Foundation 创建了许多 info 文件,这些文件用 info 程序处理。这些文件提供了丰富的导航功能,包括跳到其他小节。请用 man info 或 info info 了解更多信息。并不是所有命令都有 info 文档,所以如果您成为 info 用户,也常常需要使用手册页。 手册页还有一些图形化界面,比如 xman(来自 XFree86 项目)和 yelp(Gnome 2.0 帮助浏览器)。 如果无法找到某一命令的帮助,可以试着带 --help 选项运行这个命令。这可能会提供此命令的帮助,或者告诉您如何获得需要的帮助。 下一节讨论使用过滤器处理文本流。 文本流和过滤器 本节讨论初级管理(LPIC-1)考试 101 的主题 1.103.2 的内容。这个主题的权值是 6。 在本节中,学习以下主题: 通过文本工具过滤器发送文本文件和输出流,从而修改输出 使用 GNU textutils 包中的标准 UNIX 命令 文本过滤 文本过滤 就是获取文本输入流,在文本上执行某些转换,然后将它发送到输出流的过程。尽管输入或输出可以是文件,但是在 Linux 和 UNIX 环境中,进行过滤的最常用方式是构造命令的管道,也就是对一个命令的输出进行管道连接(即 重定向),用作下一个命令的输入。管道和重定向将在 流、管道和重定向 一节中更完整地讨论,目前,我们来看看使用 | 和 > 操作符的管道和基本输出重定向。 用 | 构造管道 前面一节提到过,shell 使用三种标准 I/O 流: stdin 是标准输入流,它向命令提供输入。 stdout 是标准输出流,它显示来自命令的输出。 stderr 是标准错误流,它显示来自命令的错误输出。 在本教程中到目前为止,输入都是来自我们提供给命令的参数,输出都显示在终端上。许多文本处理命令(过滤器)可以从标准输入流或文件获得输入。要想将一个命令(command1)的输出用作一个过滤器(command2)的输入,应该使用管道操作符(|)连接这两个命令,如清单 20 所示。 清单 20. 通过管道将 command1 的输出连接到 command2 的输入
command1 | command2
正如在本节后面会看到的,这两个命令都可能有选项或参数。还可以使用 | 将这个管道中 command2 的输出重定向到另一个命令,command3。将功能有限的多个命令连接成长的管道是在 Linux 和 UNIX 上完成任务的常用方式。有时还会看到连字符(-)用来替代文件名作为命令参数,这意味着输入应该来自 stdin 而不是文件。 用 > 进行输出重定向 能够用几个命令创建管道并在终端上看到输出固然很不错,但是有时候希望将输出保存到文件中。这要使用输出重定向操作符(>)来完成。 对于本节的其余部分,我们将使用一些小文件,所以先创建一个称为 lpi103 的目录,然后进入这个目录。然后使用 > 把 echo 命令的输出重定向到 text1 文件。这些操作见清单 21。注意,输出没有显示在终端上,因为它被重定向到文件了。 清单 21. 将 echo 命令的输出重定向到文件
[ian@echidna ian]$ mkdir lpi103 [ian@echidna ian]$ cd lpi103 [ian@echidna lpi103]$ echo -e "1 apple\n2 pear\n3 banana">text1
既然我们已经有了进行管道连接和重定向的基本工具,就来看一些常用的 UNIX 和 Linux 文本处理命令和过滤器。本节只展示一些基本功能;请通过适当的手册页进一步了解这些命令。 cat、tac、od 和 split 既然已经创建了 text1 文件,您可能想看看其中的内容。使用 cat(catenate 的简写)命令将文件的内容显示在 stdout 上。清单 22 检查上面创建的文件的内容。 清单 22. 用 cat 显示文件内容
[ian@echidna lpi103]$ cat text1 1 apple 2 pear 3 banana
如果不指定文件名(或者指定 - 为文件名),那么 cat 命令从 stdin 接收输入。让我们使用这种方式和输出重定向来创建另一个文本文件,如清单 23 所示。 清单 23. 用 cat 创建文本文件
[ian@echidna lpi103]$ cat>text2 9 plum 3 banana 10 apple
在清单 23 中,cat 从 stdin 读取输入,直到文件的末尾。使用 Ctrl-d(按住 Ctrl 并按下 d)组合键来表示文件的末尾。这个组合键也用来退出 bash shell。还要注意,制表符键帮助将这些水果名按列排列起来。 有时候,可能希望按相反的次序显示文件。很自然,也有一个用于此目的的文本过滤器,称为 tac(cat 的反序)。清单 24 按相反的次序显示新的 text2 文件和原来的 text1 文件。注意显示中如何简单地连接这两个文件。 清单 24. 用 tac 进行反序显示
[ian@echidna lpi103]$ tac text2 text1 10 apple 3 banana 9 plum 3 banana 2 pear 1 apple
现在,假设使用 cat 或 tac 显示这两个文本文件并注意到对齐方式不一样。要了解造成这个问题的原因,需要看到文件中的控制字符。因为这些控制字符的作用是进行文本显示输出,而控制字符本身没有显示,所以需要将文件转储 为另一种格式,从而能够寻找和解释这些特殊字符。GNU 文本实用程序包含的 od(即 Octal Dump)命令用于这个目的。 od 有几个选项,比如 -A 选项用来控制文件偏移量的基数,-t 选项用来控制显示的文件内容的形式。基数可以指定为 o(八进制 - 默认)、d(十进制)、x(十六进制)或 n(不显示偏移量)。可以将输出显示为八进制、十六进制、十进制、浮点、用反斜线转义的 ASCII 或命名的字符(nl 表示新行,ht 表示水平制表符,等等)。清单 25 显示一些可以用来对 text2 示例文件进行转储的格式。 清单 25. 用 od 对文件进行转储
[ian@echidna lpi103]$ od text2 0000000 004471 066160 066565 031412 061011 067141 067141 005141 0000020 030061 060411 070160 062554 000012 0000031 [ian@echidna lpi103]$ od -A d -t c text2 0000000 9 \t p l u m \n 3 \t b a n a n a \n 0000016 1 0 \t a p p l e \n 0000025 [ian@echidna lpi103]$ od -A n -t a text2 9 ht p l u m nl 3 ht b a n a n a nl 1 0 ht a p p l e nl
注意: cat 的 -A 选项也可以用来查看制表符和行末的位置。更多信息参见手册页。 如果您具有大型机背景,就可能对 hexdump 实用程序感兴趣,这是另一个实用程序集的一部分。这里不讨论它,请参考手册页。 我们的示例文件非常小,但是有时候有大型文件需要分割为比较小的块。例如,可能希望将大文件分割为 CD 容量的块,以便将它写到 CD 中。split 命令会完成这个任务,可以使用 cat 命令轻松地重新创建文件。在默认情况下,split 命令产生的文件的名称前面有前缀 'x',后面有后缀 'aa'、'ab'、'ac'、...'ba'、'bb' 等等。可以用选项控制这些默认前缀和后缀。还可以控制输出文件的大小,以及产生的文件是包含整行,还是只按字节计数。清单 26 演示将我们的两个文本文件进行分割,输出文件具有不同的前缀。我们将 text1 分割为最多包含两行的文件,将 text2 分割为最多包含 18 字节的文件。然后使用 cat 单独显示一些片段,并使用 globbing 显示完整的文件,这在本教程后面的 通配符和 globbing 一节中讨论。 清单 26. 用 split 和 cat 进行分割和重新组合
[ian@echidna lpi103]$ split -l 2 text1 [ian@echidna lpi103]$ split -b 18 text2 y [ian@echidna lpi103]$ cat yaa 9 plum 3 banana 10[ian@echidna lpi103]$ cat yab apple [ian@echidna lpi103]$ cat y* 9 plum 3 banana 10 apple
注意,分割产生的文件 yaa 并不以新行字符结束,所以在用 cat 显示它之后提示符发生了偏移。 wc、head 和 tail cat 和 tac 显示整个文件。这对于我们的示例这样的小文件是合适的,但是对于大文件就不合适了。那么,可能希望先用 wc(Word Count)命令看看文件有多大。wc 命令显示文件中的行数、单词数和字节数。还可以使用 ls -l 了解字节数。清单 27 显示以长格式列出两个文本文件的目录清单,以及 wc 的输出。 清单 27. 对文本文件使用 wc
[ian@echidna lpi103]$ ls -l text* -rw-rw-r-- 1 ian ian 24 Sep 23 12:27 text1 -rw-rw-r-- 1 ian ian 25 Sep 23 13:39 text2 [ian@echidna lpi103]$ wc text* 3 6 24 text1 3 6 25 text2 6 12 49 total
可以使用选项控制 wc 的输出,或者显示其他信息,比如最大行长度。详情请参考手册页。 有两个命令可以显示第一部分(头)或最后一部分(尾)。这些命令是 head 和 tail 命令。它们可以用作过滤器,也可以以文件名作为参数。在默认情况下,它们显示文件或流的前 10 行(或最后 10 行)。清单 28 使用 dmesg 命令显示引导消息,并使用 wc、tail 和 head 来发现共有 177 个消息,然后显示最后 10 个消息,最后显示最后 15 个消息中的前 6 个。在输出中,一些行被截断了(用 ... 表示)。 清单 28. 使用 wc、head 和 tail 来显示引导消息
[ian@echidna lpi103]$ dmesg | wc 177 1164 8366 [ian@echidna lpi103]$ dmesg | tail i810: Intel ICH2 found at IO 0x1880 and 0x1c00, MEM 0x0000 and ... i810_audio: Audio Controller supports 6 channels. i810_audio: Defaulting to base 2 channel mode. i810_audio: Resetting connection 0 ac97_codec: AC97 Audio codec, id: ADS98 (Unknown) i810_audio: AC'97 codec 0 Unable to map surround DAC's (or ... i810_audio: setting clocking to 41319 Attached scsi CD-ROM sr0 at scsi0, channel 0, id 0, lun 0 sr0: scsi3-mmc drive: 0x/32x writer cd/rw xa/form2 cdda tray Uniform CD-ROM driver Revision: 3.12 [ian@echidna lpi103]$ dmesg | tail -n15 | head -n 6 agpgart: Maximum main memory to use for agp memory: 941M agpgart: Detected Intel i845 chipset agpgart: AGP aperture is 64M @ 0xf4000000 Intel 810 + AC97 Audio, version 0.24, 13:01:43 Dec 18 2003 PCI: Setting latency timer of device 00:1f.5 to 64 i810: Intel ICH2 found at IO 0x1880 and 0x1c00, MEM 0x0000 and ...
tail 的另一种常见用法是使用 -f 选项(常常带行计数 1)跟随 一个文件。如果有一个后台进程会在文件中产生输出,而您希望检查这个文件来了解进程正在做什么,那么就可以采用这种做法。在这种模式中,tail 将一直运行到取消它为止(使用 Ctrl-c),每当行被写到文件时就显示它们。 expand、unexpand 和 tr 在创建 text1 和 text2 文件时,我们使用制表符创建 text2。有时候希望将制表符转换成空格,或者相反。expand 和 unexpand 命令用于此目的。这两个命令的 -t 选项允许设置制表符对应的空格数。清单 29 显示如何将 text2 中的制表符展开为空格,以及用另一个奇特的 expand 和 unexpand 序列使 text2 中的文本不对齐。 清单 29. 使用 expand 和 unexpand
[ian@echidna lpi103]$ expand -t 1 text2 9 plum 3 banana 10 apple [ian@echidna lpi103]$ expand -t8 text2|unexpand -a -t2|expand -t3 9 plum 3 banana 10 apple
不幸的是,不能使用 unexpand 将 text1 中的空格替换为制表符,因为 unexpand 需要至少两个空格才能转换为制表符。但是,可以使用 tr 命令来完成,这个命令将一个集合(set1)中的字符转换为另一个集合(set2)中对应的字符。清单 30 显示如何使用 tr 将空格转换为制表符。因为 tr 是一个纯粹的过滤器,所以要使用 cat 命令为它产生输出。这个示例还演示通过使用 - 让 cat 使用标准输入。 清单 30. 使用 tr
[ian@echidna lpi103]$ cat text1 |tr ' ' '\t'|cat - text2 1 apple 2 pear 3 banana 9 plum 3 banana 10 apple
如果您不确定最后这两个示例中发生了什么,那么尝试使用 od 依次终止管道的每个阶段;例如 cat text1 |tr ' ' '\t' | od -tc pr、nl 和 fmt pr 命令用于对文件进行格式化以便打印。默认的页眉包括文件名、创建文件的日期和时间以及页号,页脚是两个空行。当从多个文件或标准输入流创建输出时,当前日期和时间替代文件名和创建日期。可以通过选项分栏并列打印文件并控制格式化的许多方面。详情请参考手册页。 nl 命令对行进行编号,这在打印文件时很方便。还可以用 cat 命令的 -n 选项对行进行编号。清单 31 显示如何打印 text1,然后给 text2 加行号并将它打印在 text1 旁边。 清单 31. 加行号和格式化打印
[ian@echidna lpi103]$ pr text1 | head 2005-09-23 12:27 text1 Page 1 1 apple 2 pear 3 banana [ian@echidna lpi103]$ nl text2 | pr -m - text1 | head 2005-09-26 11:48 Page 1 1 9 plum 1 apple 2 3 banana 2 pear 3 10 apple 3 banana
另一个对于文本格式化有帮助的命令是 fmt 命令,它使文本适合页面界限。可以将几个短行合并以及将长行分割。在清单 32 中,使用 !#:* 历史特性的变体创建具有一长行文本的 text3,它把我们输入的句子保存了四次。还创建 text4,其中每个单词占一行。然后使用 cat 显示没有经过格式化时的情况,包括显示 '$' 字符来表示行末。最后,使用 fmt 将它们格式化为最大宽度 60 个字符。同样,关于其他选项的详情请参考手册页。 清单 32. 按照最大行长度进行格式化
[ian@echidna lpi103]$ echo "This is a sentence. " !#:* !#:1-$>text3 echo "This is a sentence. " "This is a sentence. " "This is a sentenc e. " "This is a sentence. ">text3 [ian@echidna lpi103]$ echo -e "This\nis\nanother\nsentence.">text4 [ian@echidna lpi103]$ cat -et text3 text4 This is a sentence. This is a sentence. This is a sentence. This i s a sentence. $ This$ is$ another$ sentence.$ [ian@echidna lpi103]$ fmt -w 60 text3 text4 This is a sentence. This is a sentence. This is a sentence. This is a sentence. This is another sentence.
sort 和 uniq sort 命令使用系统地区的整理序列(LC_COLLATE)对输入进行排序。sort 命令还可以对已经排序的文件进行合并,以及检查文件是排序的还是未排序的。 清单 33 先将 text1 中的空格转换为制表符,然后使用 sort 命令对两个文本文件进行排序。因为排序次序是按照字符决定的,您可能会对结果感到吃惊。幸运的是,sort 命令既可以按照数字值,也可以按照字符值进行排序。可以针对整个记录,也可以针对每个字段 指定这一选择。除非指定了另一种字段分隔符,否则字段是由空格或制表符分隔的。清单 33 中的第二个示例对第一个字段进行数字式排序,对第二个字段按照整理序列(字母表次序)排序。它还演示了使用 -u 选项消除任何重复的行,只保留独特的行。 清单 33. 字符和数字式排序
[ian@echidna lpi103]$ cat text1 | tr ' ' '\t' | sort - text2 10 apple 1 apple 2 pear 3 banana 3 banana 9 plum [ian@echidna lpi103]$ cat text1|tr ' ' '\t'|sort -u -k1n -k2 - text2 1 apple 2 pear 3 banana 9 plum 10 apple
注意,仍然有两行都包含水果名“apple”。另一个命令 uniq 使我们能够更进一步控制对重复行的消除。uniq 命令一般在已排序的文件上进行操作,但是无论文件是否已经排序,它都会删除 连续的 相同行。uniq 命令还可以忽略一些字段。清单 34 按照第二个字段(水果名)对两个文本文件进行排序,并消除那些从第二个字段开始的内容都相同的行(也就是说,在测试 uniq 时我们跳过了第一个字段)。 清单 34. 使用 uniq
[ian@echidna lpi103]$ cat text1|tr ' ' '\t'|sort -k2 - text2|uniq -f1 10 apple 3 banana 2 pear 9 plum
排序是按照整理序列进行的,所以 uniq 显示 “10 apple” 这一行而不是 “1 apple”。请尝试添加对第一个字段的数字式排序,从而改变这种情况。 cut、paste 和 join 现在,我们来看另外三个对文本数据中的字段进行处理的命令。这些命令对于处理表格数据尤其有帮助。第一个命令是 cut 命令,它从文本文件中提取字段。默认的字段分隔符是制表符。清单 35 使用 cut 分隔 text2 的两列,然后使用空格作为输出分隔符,这是将每行中的制表符转换为空格的另一种方式。 清单 35. 使用 cut
[ian@echidna lpi103]$ cut -f1-2 --output-delimiter=' ' text2 9 plum 3 banana 10 apple
paste 命令将来自两个或更多文件的行并列粘贴在一起,这与 pr 命令使用 -m 选项对文件进行合并的方式相似。清单 36 显示粘贴两个文本文件的结果。 清单 36. 粘贴文件
[ian@echidna lpi103]$ paste text1 text2 1 apple 9 plum 2 pear 3 banana 3 banana 10 apple
这些示例只展示了简单的粘贴,但是 paste 可以以几种其他方式从两个或更多文件粘贴数据。详情请参考手册页。 最后一个字段操作命令是 join,它根据匹配的字段对文件进行联结。文件应该是按照联结字段排序的。因为 text2 没有按照数字次序排序,我们先对它进行排序,然后将具有匹配的联结字段(在这个示例中是 3)的行联结起来。我们还创建一个新文件 text5,创建的办法是按照第二个字段(水果名)对 text1 排序,然后用制表符替换空格。然后对 text2 排序并使用第二个字段与 text5 联结,应该有两个匹配(apple 和 banana)。清单 37 演示这两个联结。 清单 37. 用联结字段联结文件
[ian@echidna lpi103]$ sort -n text2|join -1 1 -2 1 text1 - 3 banana banana [ian@echidna lpi103]$ sort -k2 text1|tr ' ' '\t'>text5 [ian@echidna lpi103]$ sort -k2 text2 | join -1 2 -2 2 text5 - apple 1 10 banana 3 3
用来进行联结的字段是分别针对每个文件指定的。例如,可以根据一个文件中的字段 3 和另一个文件中的字段 10 进行联结。 sed sed 是 stream editor(流编辑器)。有关于 sed 的几篇 developerWorks 文章和许多书籍。sed 非常强大,它能够完成的任务超出了您的想像。这里的简短介绍应该激发您对 sed 的兴趣,但是并不全面。 与到目前为止看到的许多文本命令一样,sed 可以作为过滤器,也可以从文件接收输入。输出是标准输出流。sed 将来自输入的行装载到模式空间 中,将 sed 编辑命令应用于模式空间的内容,然后将模式空间写到标准输出。sed 可以将模式空间中的几行组合起来,可以写文件、只写选择的输出或者根本不写输出。 sed 使用正则表达式语法(参见本教程后面的 用正则表达式进行搜索)来搜索和选择性地替换模式空间中的文本,以及通过编辑命令集来控制应该操作哪些文本行。保留缓冲区(hold buffer) 为文本提供临时存储。保留缓冲区可以替换模式空间、添加到模式空间中或与模式空间交换。sed 的命令比较有限,但是这些命令与正则表达式语法和保留缓冲区结合起来就可以实现某些令人吃惊的功能。sed 命令集常常称为 sed 脚本。 清单 38 显示了三个简单的 sed 脚本。在第一个脚本中,使用 s(替换)命令用大写替换每行上的小写 'a'。这个示例只替换第一个 'a',所以在第二个示例中,添加一个 'g'(全局)标志使 sed 修改文本中出现的所有 'a'。在第三个脚本中,使用 d(删除)命令删除一行。在示例中,使用地址 2 表示只应该删除第二行。用分号(;)分隔命令并使用第二个脚本中使用过的全局替换。 清单 38. 简单的 sed 脚本
[ian@echidna lpi103]$ sed 's/a/A/' text1 1 Apple 2 peAr 3 bAnana [ian@echidna lpi103]$ sed 's/a/A/g' text1 1 Apple 2 peAr 3 bAnAnA [ian@echidna lpi103]$ sed '2d;$s/a/A/g' text1 1 apple 3 bAnAnA
除了操作单独的行之外,sed 还可以操作一个范围内的行。范围的开始和结束由一个逗号(,)分隔,可以用行号指定,脱字符(^)表示文件的开头,美圆符号($)表示文件的末尾。给出一个地址或地址范围,就可以在花括号({ 和 })之间组合几个命令,让这些命令只应用于范围内的行。清单 39 演示了将全局替换只应用于文件的最后两行的两种方式。它还演示了使用 -e 选项将命令添加到模式空间中。在使用花括号时,必须以这种方式分隔命令。 清单 39. sed 地址
[ian@echidna lpi103]$ sed -e '2,${' -e 's/a/A/g' -e '}' text1 1 apple 2 peAr 3 bAnAnA [ian@echidna lpi103]$ sed -e '/pear/,/bana/{' -e 's/a/A/g' -e '}' text1 1 apple 2 peAr 3 bAnAnA
sed 脚本还可以存储在文件中。实际上,对于频繁使用的脚本,很可能希望这么做。在前面,我们使用 tr 命令将 text1 中的空格改为制表符。现在用一个存储在文件中的 sed 脚本来完成这个任务。将使用 echo 命令创建文件。结果见清单 40。 清单 40. sed one-liner
[ian@echidna lpi103]$ echo -e "s/ /\t/g">sedtab [ian@echidna lpi103]$ cat sedtab s/ / /g [ian@echidna lpi103]$ sed -f sedtab text1 1 apple 2 pear 3 banana
有许多像清单 40 这样的方便的 sed one-liner。 最后一个 sed 示例使用 = 命令打印行号,然后通过 sed 对产生的输出进行过滤,从而模仿 nl 命令对行进行编号的效果。清单 41 使用 = 来打印行号,然后使用 N 命令将第二个输入行读入模式空间,最后删除模式空间中两行之间的新行字符(\n)。 清单 41. 用 sed 对行进行编号
[ian@echidna lpi103]$ sed '=' text2 1 9 plum 2 3 banana 3 10 apple [ian@echidna lpi103]$ sed '=' text2|sed 'N;s/\n//' 19 plum 23 banana 310 apple
结果不太理想!我们其实希望将行号在一栏中对齐,与文件中的行分开。在清单 42 中,我们输入几行命令(注意 > 辅助提示符)。请研究这个示例并参考下面的解释。 清单 42. 用 sed 对行进行编号 - 修正版
[ian@echidna lpi103]$ cat text1 text2 text1 text2>text6 [ian@echidna lpi103]$ ht=$(echo -en "\t") [ian@echidna lpi103]$ sed '=' text6|sed "N > s/^/ / > s/^.*\(......\)\n/\1$ht/" 1 1 apple 2 2 pear 3 3 banana 4 9 plum 5 3 banana 6 10 apple 7 1 apple 8 2 pear 9 3 banana 10 9 plum 11 3 banana 12 10 apple
我们采取的步骤是这样的: 先使用 cat 创建一个有 12 行的文件,其内容来自 text1 和 text2 文件的两个副本。产生 12 行是为了显示两位数的行号,从而表现栏中数字格式化的效果。 bash shell 使用制表符键结束命令,所以在需要真正的制表符时如果可以使用代表制表符的字符,这样会很方便。我们使用 echo 命令实现这个字符表示并将它存储在 shell 变量 'ht' 中。 像前面一样,创建包含行号和数据行的流,并通过 sed 的第二个副本对它进行过滤。 将第二行读入模式空间。 在模式空间的开头(由 ^ 表示)加上我们的行号以及六个空格。 然后用新行字符前面的最后六个字符加制表符来替换新行前的所有内容。注意,'s' 命令的左边部分使用 '\(' 和 '\)' 表示在右边部分中要使用的字符。在右边,用 \1 引用第一个这样表示的字符集(在这个示例中只有一个字符集)。注意,命令包含在双引号(")中,所以会对 $ht 进行替换。 sed 最近的版本(version 4)包含 info 格式的文档,还有许多出色的示例。在 version 3.02 中没有这些。GNU sed 在接到 sed --version 命令时会显示版本。
|